#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>

#include "public.h"
#include "chatcli.h"

ChatCli::ChatCli(const char *ipaddr, int port):
    sock (-1), 
    ipaddr (ipaddr),
    port (port) {
    if (0 > (sock = socket (PF_INET, SOCK_DGRAM, 0)))
        ERR_EXIT ("socket");

    memset (&servaddr, 0, sizeof (servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons (port);
    servaddr.sin_addr.s_addr = inet_addr (ipaddr);
}

ChatCli::~ChatCli() {
    if (-1 != sock) {
        close (sock);
    }
}

bool ChatCli::start() {
    MESSAGE msg;
    while (1) {
        memset (username,0,sizeof(username));
        printf ("please inpt your name:");
        fflush (stdout);
        scanf ("%s", username);

        memset (&msg, 0, sizeof (msg));
        msg.cmd = htonl (C2S_LOGIN);
        strcpy (msg.body, username);

        if (-1 == sendto (sock, &msg, sizeof (msg), 0, (struct sockaddr *)&servaddr, sizeof (servaddr))) {
            ERR_EXIT ("sendto");
        }

        memset (&msg, 0, sizeof (msg));
        if (-1 == recvfrom (sock, &msg, sizeof (msg), 0, NULL, NULL)) {
            ERR_EXIT ("recvfrom");
        }
        int cmd = ntohl (msg.cmd);
        if (cmd == S2C_ALREADY_LOGINED) {
            printf ("user %s already logined server, please use another username\n", username);
        } else if (cmd == S2C_LOGIN_OK) {
            printf ("user %s has logined server\n", username);
            break;
        } else {
            printf ("Server return err, please use another username\n");
        }
    }
    if (-1 == recvfrom(sock, &count, sizeof(int), 0, NULL, NULL)) {
        ERR_EXIT ("recvfrom");
    }

    count = ntohl (count);
    printf ("%d users logined server\n", count);
    for (int i=0; i<count; i++) {
        USER_INFO user;
        recvfrom (sock, &user, sizeof (USER_INFO), 0, NULL, NULL);
        client_list.push_back (user);
        in_addr tmp;
        tmp.s_addr = user.ip;

        printf ("%d %s <-> %s:%d\n", i, user.username, inet_ntoa(tmp), ntohs(user.port));
    }

    printf ("\nCommands are:\n");
    printf ("send username msg\n");
    printf ("list\n");
    printf ("exit\n");
    printf ("\n");
    return true;
}

void ChatCli::run() {
    MESSAGE msg;
    fd_set rset;
    FD_ZERO (&rset);
    int nready;
    sockaddr_in peeraddr;
    memset (&peeraddr, 0, sizeof (peeraddr));
    socklen_t peerlen = sizeof (peeraddr);
    while (1) {
        FD_SET (STDIN_FILENO, &rset);
        FD_SET (sock, &rset);
        nready = select (sock+1, &rset, NULL, NULL, NULL);
        if (nready == -1) {
            ERR_EXIT ("select");
        }

        if (nready == 0) {
            continue;
        }

        if (FD_ISSET (sock, &rset))
        {
            peerlen = sizeof (peeraddr);
            memset (&msg, 0, sizeof (msg));
            recvfrom (sock, &msg, sizeof (msg), 0, (struct sockaddr *)&peeraddr, &peerlen);
            int cmd = ntohl (msg.cmd);
            switch (cmd) {
            case S2C_SOMEONE_LOGIN:
                do_someone_login (msg);
                break;
            case S2C_SOMEONE_LOGOUT:
                do_someone_logout (msg);
                break;
            case S2C_ONLINE_USER:
                do_getlist ();
                break;
            case C2C_CHAT:
                do_chat (msg);
                break;
            default:
                break;
            }
        }
        if (FD_ISSET(STDIN_FILENO, &rset))
        {
            char cmdline[100] = {0};
            if (fgets (cmdline, sizeof (cmdline), stdin) == NULL) {
                break;
            }
            if (cmdline[0] == '\n') {
                continue;
            }

            cmdline[strlen (cmdline) - 1] = '\0';
            parseCmd (cmdline);
        }
    }
    logout ();
}

void ChatCli::parseCmd(char *cmdline) {
    char cmd[10]={0};
    char *p;
    p = strchr(cmdline, ' ');
    if (p != NULL)
        *p = '\0';

    strcpy(cmd, cmdline);
    
    if (strcmp(cmd, "exit") == 0) {
        logout ();
        printf("user %s has logout server\n", username);
        exit(EXIT_SUCCESS);

    } else if (strcmp(cmd, "send") == 0) {
        char peername[16]={0};
        char msg[MSG_LEN]={0};

        /* send  user  msg  */
        /*       p     p2   */
        while (*p++ == ' ') ;
        char *p2;
        p2 = strchr(p, ' ');
        if (p2 == NULL) {
            printf("bad command\n");
            printf("\nCommands are:\n");
            printf("send username msg\n");
            printf("list\n");
            printf("exit\n");
            printf("\n");
            return;
        }
        *p2 = '\0';
        strcpy(peername, p);

        while (*p2++ == ' ') ;
        strcpy(msg, p2);
        sendMsgTo(peername, msg);
    } else if (strcmp(cmd, "list") == 0) {
        getUsrList ();
    } else {
        printf("bad command\n");
        printf("\nCommands are:\n");
        printf("send username msg\n");
        printf("list\n");
        printf("exit\n");
        printf("\n");
    }
}

bool ChatCli::sendMsgTo(char *name, char *msg)
{
    if (0 == strcmp (name, username))
    {
        printf("can't send message to self\n");
        return false;
    }

    USER_LIST::iterator it;
    for (it = client_list.begin(); it != client_list.end(); ++it)
    {
        if (0 == strcmp (it->username,name))
            break;
    }

    if (it == client_list.end())
    {
        printf("user %s has not logined server\n", name);
        return false;
    }

    MESSAGE m;
    memset(&m,0,sizeof(m));
    m.cmd = htonl(C2C_CHAT);

    CHAT_MSG cm;
    strcpy(cm.username, username);
    strcpy(cm.msg, msg);

    memcpy(m.body, &cm, sizeof(cm));
    //strcpy(m.body,msg);

    struct sockaddr_in    peeraddr;
    memset(&peeraddr,0,sizeof(peeraddr));
    peeraddr.sin_family      = AF_INET;
    peeraddr.sin_addr.s_addr = it->ip;
    peeraddr.sin_port        = it->port;

    in_addr tmp;
    tmp.s_addr = it->ip;

    printf("sending message [%s] to user [%s] <-> %s:%d\n",  msg, name, inet_ntoa(tmp), ntohs(it->port));

    if (-1 == sendto(sock, (const char*)&m, sizeof(m), 0, (struct sockaddr *)&peeraddr, sizeof(peeraddr))) {
        ERR_EXIT("sendto");
    }
    return true;
}

void ChatCli::logout() {
    MESSAGE msg;
    memset (&msg, 0, sizeof (msg));
    msg.cmd = htonl (C2S_LOGOUT);
    strcpy (msg.body, username);

    if (-1 == sendto(sock, (const char*)&msg, sizeof(msg), 0, (struct sockaddr *)&servaddr, sizeof(servaddr))) {
        ERR_EXIT("sendto");
    }
}

void ChatCli::getUsrList() {
    MESSAGE msg;
    memset(&msg, 0, sizeof(msg));
    msg.cmd = htonl(C2S_ONLINE_USER);

    if (-1 == sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr *)&servaddr, sizeof(servaddr))) {
        ERR_EXIT("sendto");
    }
}

void ChatCli::do_someone_login(MESSAGE &msg)
{
    USER_INFO *user = (USER_INFO*)msg.body;
    in_addr tmp;
    tmp.s_addr = user->ip;
    printf("%s <-> %s:%d has logined server\n", user->username, inet_ntoa(tmp), ntohs(user->port));
    client_list.push_back(*user);
}

void ChatCli::do_someone_logout(MESSAGE &msg)
{
    USER_LIST::iterator it;
    for (it=client_list.begin(); it != client_list.end(); ++it)
    {
        if (strcmp(it->username,msg.body) == 0)
            break;
    }

    if (it != client_list.end())
        client_list.erase(it);

    printf("user %s has logout server\n", msg.body);
}

void ChatCli::do_getlist()
{
    recvfrom(sock, &count, sizeof(int), 0, NULL, NULL);
    printf("has %d users logined server\n", ntohl(count));
    client_list.clear();

    count = ntohl(count);
    for (int i = 0; i < count; i++)
    {
        USER_INFO user;
        recvfrom(sock, &user, sizeof(USER_INFO), 0, NULL, NULL);
        client_list.push_back(user);
        in_addr tmp;
        tmp.s_addr = user.ip;

        printf("%s <-> %s:%d\n", user.username, inet_ntoa(tmp), ntohs(user.port));
    }
}

void ChatCli::do_chat(MESSAGE &msg)
{
    CHAT_MSG *cm = (CHAT_MSG*)msg.body;
    printf("recv a msg [%s] from [%s]\n", cm->msg, cm->username);
    //recvfrom(sock, &count, sizeof(int), 0, NULL, NULL);
}

int main (void) {
    ChatCli cli;
    cli.start ();
    cli.run ();
}
